package main

import (
	"encoding/binary"
	"fmt"
	"github.com/tendermint/tendermint/abci/example/code"
	abcitypes "github.com/tendermint/tendermint/abci/types"
)


type CounterApplication struct {
	abcitypes.BaseApplication
	hashCount int
	txCount int  // 交易数量  序号值
	serial bool
}


func NewCounterApplication(serial bool) *CounterApplication {
	return &CounterApplication{
		serial:serial,
	}
}

func (app CounterApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo {
	// 返回{hashes:,txs:}
	return abcitypes.ResponseInfo{Data: fmt.Sprintf("{\"hashes\":%v,\"txs\":%v}", app.hashCount, app.txCount)}
}


// 启动参数 --serial
func (app CounterApplication) SetOption(req abcitypes.RequestSetOption) abcitypes.ResponseSetOption {
	key, value := req.Key, req.Value

	if key == "serial" && value == "on" {
		app.serial = true
	}

	return abcitypes.ResponseSetOption{}
}

// 我们创建一批来存储块的交易
func (app *CounterApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {

	if app.serial{

		if len(req.Tx)>8{
			return abcitypes.ResponseDeliverTx{
				Code:                 code.CodeTypeEncodingError,
				Log:                  fmt.Sprintf("Max tx size is 8 bytes, got %d", len(req.Tx))}
		}
		//  [][]byte  维度是8
		// [0][]byte
		//[1][]byte
		// ...
		tx8:=make([]byte,8)

		copy(tx8[len(tx8)-len(req.Tx):], req.Tx)

		txValue := binary.BigEndian.Uint64(tx8)

		if txValue != uint64(app.txCount) {
			return abcitypes.ResponseDeliverTx{
				Code:                 code.CodeTypeBadNonce,
				Log:  fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}

		}

	}
	app.txCount++
	return abcitypes.ResponseDeliverTx{Code:code.CodeTypeOK}


}

func (app *CounterApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
	if app.serial {
		if len(req.Tx) > 8 {
			return abcitypes.ResponseCheckTx{
				Code: code.CodeTypeEncodingError,
				Log:  fmt.Sprintf("Max tx size is 8 bytes, got %d", len(req.Tx))}
		}
		tx8 := make([]byte, 8)
		copy(tx8[len(tx8)-len(req.Tx):], req.Tx)
		txValue := binary.BigEndian.Uint64(tx8)
		if txValue < uint64(app.txCount) {
			return abcitypes.ResponseCheckTx{
				Code: code.CodeTypeBadNonce,
				Log:  fmt.Sprintf("Invalid nonce. Expected >= %v, got %v", app.txCount, txValue)}
		}
	}
	return abcitypes.ResponseCheckTx{Code: code.CodeTypeOK}
}

func (app *CounterApplication) Commit() abcitypes.ResponseCommit {
	app.hashCount++
	if app.txCount == 0 {
		return abcitypes.ResponseCommit{}
	}
	hash := make([]byte, 8)  //
	binary.BigEndian.PutUint64(hash, uint64(app.txCount))
	return abcitypes.ResponseCommit{Data: hash}
}

func (app *CounterApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) {

	switch reqQuery.Path {

	case "hash":
		return abcitypes.ResponseQuery{Value: []byte(fmt.Sprintf("%v", app.hashCount))}
	case "tx":
		return abcitypes.ResponseQuery{Value: []byte(fmt.Sprintf("%v", app.txCount))}
	default:
		return abcitypes.ResponseQuery{Log: fmt.Sprintf("Invalid query path. Expected hash or tx, got %v", reqQuery.Path)}
	}
}

func (CounterApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
	return abcitypes.ResponseInitChain{}
}


func (app *CounterApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
	return abcitypes.ResponseBeginBlock{}
}

func (CounterApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {
	return abcitypes.ResponseEndBlock{}
}
